{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "4be2e6fa-2187-4617-8433-0db4fb0c099c",
   "metadata": {},
   "source": [
    "# LangChain 核心模块学习：Model I/O\n",
    "\n",
    "`Model I/O` 是 LangChain 为开发者提供的一套面向 LLM 的标准化模型接口，包括模型输入（Prompts）、模型输出（Output Parsers）和模型本身（Models）。\n",
    "\n",
    "- Prompts：模板化、动态选择和管理模型输入\n",
    "- Models：以通用接口调用语言模型\n",
    "- Output Parser：从模型输出中提取信息，并规范化内容\n",
    "\n",
    "![](../images/model_io.jpeg)\r\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "fe06fea2-88a5-4036-a9f6-69c27b8363cd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: langchain in c:\\users\\lenovo\\appdata\\roaming\\python\\python310\\site-packages (0.2.7)\n",
      "Requirement already satisfied: PyYAML>=5.3 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from langchain) (6.0.1)\n",
      "Requirement already satisfied: SQLAlchemy<3,>=1.4 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from langchain) (2.0.31)\n",
      "Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from langchain) (3.9.5)\n",
      "Requirement already satisfied: async-timeout<5.0.0,>=4.0.0 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from langchain) (4.0.3)\n",
      "Requirement already satisfied: langchain-core<0.3.0,>=0.2.12 in c:\\users\\lenovo\\appdata\\roaming\\python\\python310\\site-packages (from langchain) (0.2.13)\n",
      "Requirement already satisfied: langchain-text-splitters<0.3.0,>=0.2.0 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from langchain) (0.2.0)\n",
      "Requirement already satisfied: langsmith<0.2.0,>=0.1.17 in c:\\users\\lenovo\\appdata\\roaming\\python\\python310\\site-packages (from langchain) (0.1.85)\n",
      "Requirement already satisfied: numpy<2,>=1 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from langchain) (1.26.4)\n",
      "Requirement already satisfied: pydantic<3,>=1 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from langchain) (2.5.3)\n",
      "Requirement already satisfied: requests<3,>=2 in c:\\users\\lenovo\\appdata\\roaming\\python\\python310\\site-packages (from langchain) (2.32.3)\n",
      "Requirement already satisfied: tenacity!=8.4.0,<9.0.0,>=8.1.0 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from langchain) (8.4.2)\n",
      "Requirement already satisfied: aiosignal>=1.1.2 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.3.1)\n",
      "Requirement already satisfied: attrs>=17.3.0 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (23.2.0)\n",
      "Requirement already satisfied: frozenlist>=1.1.1 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.4.1)\n",
      "Requirement already satisfied: multidict<7.0,>=4.5 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (6.0.5)\n",
      "Requirement already satisfied: yarl<2.0,>=1.0 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.9.4)\n",
      "Requirement already satisfied: jsonpatch<2.0,>=1.33 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from langchain-core<0.3.0,>=0.2.12->langchain) (1.33)\n",
      "Requirement already satisfied: packaging<25,>=23.2 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from langchain-core<0.3.0,>=0.2.12->langchain) (23.2)\n",
      "Requirement already satisfied: orjson<4.0.0,>=3.9.14 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from langsmith<0.2.0,>=0.1.17->langchain) (3.10.5)\n",
      "Requirement already satisfied: annotated-types>=0.4.0 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from pydantic<3,>=1->langchain) (0.6.0)\n",
      "Requirement already satisfied: pydantic-core==2.14.6 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from pydantic<3,>=1->langchain) (2.14.6)\n",
      "Requirement already satisfied: typing-extensions>=4.6.1 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from pydantic<3,>=1->langchain) (4.12.2)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from requests<3,>=2->langchain) (3.3.2)\n",
      "Requirement already satisfied: idna<4,>=2.5 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from requests<3,>=2->langchain) (3.6)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from requests<3,>=2->langchain) (2.2.2)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from requests<3,>=2->langchain) (2024.2.2)\n",
      "Requirement already satisfied: greenlet!=0.4.17 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from SQLAlchemy<3,>=1.4->langchain) (3.0.3)\n",
      "Requirement already satisfied: jsonpointer>=1.9 in c:\\programdata\\anaconda3\\envs\\langchain\\lib\\site-packages (from jsonpatch<2.0,>=1.33->langchain-core<0.3.0,>=0.2.12->langchain) (3.0.0)\n"
     ]
    }
   ],
   "source": [
    "! pip install -U langchain"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ce4a2474-0b69-4830-85cd-3715c22df304",
   "metadata": {},
   "source": [
    "## 模型输入 Prompts\n",
    "\n",
    "一个语言模型的提示是用户提供的一组指令或输入，用于引导模型的响应，帮助它理解上下文并生成相关和连贯的基于语言的输出，例如回答问题、完成句子或进行对话。\n",
    "\n",
    "\n",
    "- 提示模板（Prompt Templates）：参数化的模型输入\n",
    "- 示例选择器（Example Selectors）：动态选择要包含在提示中的示例\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1f14f4cf-8e30-47ab-b8b1-d58a90b5b1c1",
   "metadata": {},
   "source": [
    "## 提示模板 Prompt Templates\n",
    "\n",
    "**Prompt Templates 提供了一种预定义、动态注入、模型无关和参数化的提示词生成方式，以便在不同的语言模型之间重用模板。**\n",
    "\n",
    "一个模板可能包括指令、少量示例以及适用于特定任务的具体背景和问题。\n",
    "\n",
    "通常，提示要么是一个字符串（LLMs），要么是一组聊天消息（Chat Model）。\n",
    "\n",
    "\n",
    "类继承关系:\n",
    "\n",
    "```\n",
    "BasePromptTemplate --> PipelinePromptTemplate\n",
    "                       StringPromptTemplate --> PromptTemplate\n",
    "                                                FewShotPromptTemplate\n",
    "                                                FewShotPromptWithTemplates\n",
    "                       BaseChatPromptTemplate --> AutoGPTPrompt\n",
    "                                                  ChatPromptTemplate --> AgentScratchPadChatPromptTemplate\n",
    "\n",
    "\n",
    "\n",
    "BaseMessagePromptTemplate --> MessagesPlaceholder\n",
    "                              BaseStringMessagePromptTemplate --> ChatMessagePromptTemplate\n",
    "                                                                  HumanMessagePromptTemplate\n",
    "                                                                  AIMessagePromptTemplate\n",
    "                                                                  SystemMessagePromptTemplate\n",
    "\n",
    "PromptValue --> StringPromptValue\n",
    "                ChatPromptValue\n",
    "```\n",
    "\n",
    "\n",
    "**代码实现：https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/prompts**\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a40c4a43-530b-4ee1-af40-e2c90c0c8c66",
   "metadata": {},
   "source": [
    "### 使用 PromptTemplate 类生成提升词\n",
    "\n",
    "**通常，`PromptTemplate` 类的实例，使用Python的`str.format`语法生成模板化提示；也可以使用其他模板语法（例如jinja2）。**\n",
    "\n",
    "#### 使用 from_template 方法实例化 PromptTemplate"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "8d681566-cde1-4ae5-8cd7-f53cf59c3e36",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tell me a funny joke about chickens.\n"
     ]
    }
   ],
   "source": [
    "from langchain import PromptTemplate\n",
    "\n",
    "prompt_template = PromptTemplate.from_template(\n",
    "    \"Tell me a {adjective} joke about {content}.\"\n",
    ")\n",
    "\n",
    "# 使用 format 生成提示\n",
    "prompt = prompt_template.format(adjective=\"funny\", content=\"chickens\")\n",
    "print(prompt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "f09e2b7f-d1e1-4cc3-bf9b-22de07df5513",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "input_variables=['adjective', 'content'] template='Tell me a {adjective} joke about {content}.'\n"
     ]
    }
   ],
   "source": [
    "print(prompt_template)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "7c329075-0a6a-4d17-9cc3-081be37f7917",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tell me a joke\n"
     ]
    }
   ],
   "source": [
    "prompt_template = PromptTemplate.from_template(\n",
    "    \"Tell me a joke\"\n",
    ")\n",
    "# 生成提示\n",
    "prompt = prompt_template.format()\n",
    "print(prompt)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13696d97-9c8f-4530-92ec-a9cdf2012bf7",
   "metadata": {},
   "source": [
    "#### 使用构造函数（Initializer）实例化 PromptTemplate\n",
    "\n",
    "使用构造函数实例化 `prompt_template` 时必须传入参数：`input_variables` 和 `template`。\n",
    "\n",
    "在生成提示过程中，会检查输入变量与模板字符串中的变量是否匹配，如果不匹配，则会引发异常；"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "10e7686a-dcca-4410-9263-6e941bee9c55",
   "metadata": {},
   "outputs": [],
   "source": [
    "invalid_prompt = PromptTemplate(\n",
    "    input_variables=[\"adjective\"],\n",
    "    template=\"Tell me a {adjective} joke about {content}.\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d39cd8e-99a9-4d57-aff9-0cbc673336df",
   "metadata": {},
   "source": [
    "传入 content 后才能生成可用的 prompt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "49767dc4-2057-4fee-8fb0-24436194c8d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "valid_prompt = PromptTemplate(\n",
    "    input_variables=[\"adjective\", \"content\"],\n",
    "    template=\"Tell me a {adjective} joke about {content}.\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "516cee06-9673-4c9d-9499-1355034ad82a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "input_variables=['adjective', 'content'] template='Tell me a {adjective} joke about {content}.'\n"
     ]
    }
   ],
   "source": [
    "print(valid_prompt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "55c7a677-4ae8-4c81-829c-49fd597c45bc",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Tell me a funny joke about chickens.'"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "valid_prompt.format(adjective=\"funny\", content=\"chickens\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4eeb638c-7033-4cf2-9354-6481170c9892",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "754269b1-a2a7-487e-85b3-3be916be581e",
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt_template = PromptTemplate.from_template(\n",
    "    \"讲{num}个给程序员听得笑话\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "bc708563-8556-466d-9ea5-9f41c8e6a7f9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "prompt: 讲2个给程序员听得笑话\n",
      "result: \n",
      "\n",
      "1. 为什么程序员喜欢用黑客帝国里的人物作为用户名？因为他们喜欢用Neo、Trinity、Morpheus来重置密码。\n",
      "\n",
      "2. 有一个程序员去买牛奶，店员问他需要几瓶，他回答说：“我只需要一个容器，容量为1L，里面装满牛奶即可。”\n"
     ]
    }
   ],
   "source": [
    "from langchain_openai import OpenAI\n",
    "\n",
    "llm = OpenAI(model_name=\"gpt-3.5-turbo-instruct\", max_tokens=1000)\n",
    "\n",
    "prompt = prompt_template.format(num=2)\n",
    "print(f\"prompt: {prompt}\")\n",
    "\n",
    "result = llm.invoke(prompt)\n",
    "print(f\"result: {result}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "5c6dd3ea-084a-4426-bba0-2676d42842c7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "1. 为什么程序员喜欢用黑客帝国里的绿色数字做桌面壁纸？因为那样可以让电脑跑得更快。\n",
      "\n",
      "2. 为什么程序员总是喜欢喝咖啡？因为咖啡是唯一能让他们的代码跑得更快的饮料。\n",
      "\n",
      "3. 一个程序员去参加面试，面试官问他：\"如果你有10个苹果，你能用什么方法把它们平均分成5份？\"程序员回答：\"简单，我把苹果放进数组里，然后用循环把它们分成5份就可以了。\"面试官深思熟虑后说：\"那如果我只给你9个苹果呢？\"程序员回答：\"那就给我一个苹果，然后让我再写一个循环。\"\n"
     ]
    }
   ],
   "source": [
    "print(llm.invoke(prompt_template.format(num=3)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1890a0b3-c5af-4d0e-b1a1-3439f3199edc",
   "metadata": {},
   "source": [
    "#### 使用 jinja2 生成模板化提示"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "4e98620d-3462-4a7d-b9ef-1a9a0fd1f496",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Tell me a funny joke about chickens'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "jinja2_template = \"Tell me a {{ adjective }} joke about {{ content }}\"\n",
    "prompt = PromptTemplate.from_template(jinja2_template, template_format=\"jinja2\")\n",
    "\n",
    "prompt.format(adjective=\"funny\", content=\"chickens\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "3b707a3f-c6c1-4aa1-9ad2-4067c0430e26",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "input_variables=['adjective', 'content'] template='Tell me a {{ adjective }} joke about {{ content }}' template_format='jinja2'\n"
     ]
    }
   ],
   "source": [
    "print(prompt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "096c08d8-14ee-459e-9a5c-f0d659d08d34",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "91b48d41-608c-46e4-ac05-882b05cbfec2",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "c34c3078-3ece-4708-8bf8-cdc02968ea70",
   "metadata": {},
   "source": [
    "#### 实测：生成多种编程语言版本的快速排序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "c73de848-8091-4ebd-9127-ea33582bad79",
   "metadata": {},
   "outputs": [],
   "source": [
    "sort_prompt_template = PromptTemplate.from_template(\n",
    "    \"生成可执行的快速排序 {programming_language} 代码\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "f3c2ee81-80aa-4ad9-b936-8c2e1b2ba27e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "+\n",
      "+`def quick_sort(nums):\n",
      "+    if len(nums) <= 1:\n",
      "+        return nums\n",
      "+    pivot = nums[len(nums)//2]\n",
      "+    left = [x for x in nums if x < pivot]\n",
      "+    middle = [x for x in nums if x == pivot]\n",
      "+    right = [x for x in nums if x > pivot]\n",
      "+    return quick_sort(left) + middle + quick_sort(right)`\n",
      "+\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(llm.invoke(sort_prompt_template.format(programming_language=\"python\")))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "ba3b370e-a647-4913-b4a0-c33d4dd11e35",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "public class QuickSort {\n",
      "\n",
      "  // 快速排序\n",
      "  public static void quickSort(int[] arr, int start, int end) {\n",
      "\n",
      "    // 如果开始索引小于结束索引，则继续排序\n",
      "    if (start < end) {\n",
      "\n",
      "      // 以第一个元素为基准，将数组分成两部分\n",
      "      int pivot = arr[start];\n",
      "      int i = start;\n",
      "      int j = end;\n",
      "\n",
      "      // 将小于基准的元素放在左边，大于基准的元素放在右边\n",
      "      while (i < j) {\n",
      "        // 从右往左找到第一个小于基准的元素\n",
      "        while (i < j && arr[j] >= pivot) {\n",
      "          j--;\n",
      "        }\n",
      "        if (i < j) {\n",
      "          arr[i] = arr[j];\n",
      "          i++;\n",
      "        }\n",
      "        // 从左往右找到第一个大于基准的元素\n",
      "        while (i < j && arr[i] < pivot) {\n",
      "          i++;\n",
      "        }\n",
      "        if (i < j) {\n",
      "          arr[j] = arr[i];\n",
      "          j--;\n",
      "        }\n",
      "      }\n",
      "\n",
      "      // 将基准元素放在中间\n",
      "      arr[i] = pivot;\n",
      "\n",
      "      // 递归排序左边和右边的子数组\n",
      "      quickSort(arr, start, i - 1);\n",
      "      quickSort(arr, i + 1, end);\n",
      "    }\n",
      "  }\n",
      "\n",
      "  public static void main(String[] args) {\n",
      "\n",
      "    // 待排序数组\n",
      "    int[] arr = { 5, 2, 9, 3, 7, 1, 6, 8, 4, 0 };\n",
      "\n",
      "    // 调用快速排序\n",
      "    quickSort(arr, 0, arr.length - 1);\n",
      "\n",
      "    // 打印排序后的数组\n",
      "    System.out.println(Arrays.toString(arr));\n",
      "  }\n",
      "}\n",
      "\n",
      "// 输出结果：[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n"
     ]
    }
   ],
   "source": [
    "print(llm.invoke(sort_prompt_template.format(programming_language=\"java\")))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "10ef61ce-af9f-40d6-970e-cdacc4d4736a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "C++ 代码\n",
      "\n",
      "#include <iostream>\n",
      "using namespace std;\n",
      "\n",
      "// 快速排序函数\n",
      "void quickSort(int arr[], int low, int high) {\n",
      "    // 设置基准数\n",
      "    int pivot = arr[low];\n",
      "    int i = low;\n",
      "    int j = high;\n",
      "\n",
      "    // 循环比较\n",
      "    while (i < j) {\n",
      "        // 从右向左找小于基准数的数\n",
      "        while (i < j && arr[j] >= pivot) {\n",
      "            j--;\n",
      "        }\n",
      "        // 将小于基准数的数放到左边\n",
      "        arr[i] = arr[j];\n",
      "        \n",
      "        // 从左向右找大于基准数的数\n",
      "        while (i < j && arr[i] <= pivot) {\n",
      "            i++;\n",
      "        }\n",
      "        // 将大于基准数的数放到右边\n",
      "        arr[j] = arr[i];\n",
      "    }\n",
      "\n",
      "    // 将基准数放到正确的位置\n",
      "    arr[i] = pivot;\n",
      "\n",
      "    // 递归调用\n",
      "    if (low < i) {\n",
      "        quickSort(arr, low, i - 1);\n",
      "    }\n",
      "    if (high > i) {\n",
      "        quickSort(arr, i + 1, high);\n",
      "    }\n",
      "}\n",
      "\n",
      "// 主函数\n",
      "int main() {\n",
      "    // 定义数组\n",
      "    int arr[10] = {5, 8, 2, 6, 1, 9, 3, 7, 4, 0};\n",
      "\n",
      "    // 输出排序前的数组\n",
      "    cout << \"排序前的数组：\";\n",
      "    for (int i = 0; i < 10; i++) {\n",
      "        cout << arr[i] << \" \";\n",
      "    }\n",
      "    cout << endl;\n",
      "\n",
      "    // 调用快速排序函数\n",
      "    quickSort(arr, 0, 9);\n",
      "\n",
      "    // 输出排序后的数组\n",
      "    cout << \"排序后的数组：\";\n",
      "    for (int i = 0; i < 10; i++) {\n",
      "        cout << arr[i] << \" \";\n",
      "    }\n",
      "    cout << endl;\n",
      "\n",
      "    return 0;\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "print(llm.invoke(sort_prompt_template.format(programming_language=\"C++\")))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "510954a1-2c16-414c-ada1-af9d439d43be",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "621af4b1-0337-428a-bc88-17f1ff1f876e",
   "metadata": {},
   "source": [
    "## 使用 ChatPromptTemplate 类生成适用于聊天模型的聊天记录\n",
    "\n",
    "**`ChatPromptTemplate` 类的实例，使用`format_messages`方法生成适用于聊天模型的提示。**\n",
    "\n",
    "### 使用 from_messages 方法实例化 ChatPromptTemplate"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "875c8534-7317-4111-9658-80a926458168",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.prompts import ChatPromptTemplate\n",
    "\n",
    "template = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", \"You are a helpful AI bot. Your name is {name}.\"),\n",
    "    (\"human\", \"Hello, how are you doing?\"),\n",
    "    (\"ai\", \"I'm doing well, thanks!\"),\n",
    "    (\"human\", \"{user_input}\"),\n",
    "])\n",
    "\n",
    "# 生成提示\n",
    "messages = template.format_messages(\n",
    "    name=\"Bob\",\n",
    "    user_input=\"What is your name?\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "ceb1c89b-c71a-4e7c-bf81-2f2a5ddcf80e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[SystemMessage(content='You are a helpful AI bot. Your name is Bob.'), HumanMessage(content='Hello, how are you doing?'), AIMessage(content=\"I'm doing well, thanks!\"), HumanMessage(content='What is your name?')]\n"
     ]
    }
   ],
   "source": [
    "print(messages)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "a99257dd-3dad-48bd-9c75-1ab9348179ff",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "You are a helpful AI bot. Your name is Bob.\n",
      "What is your name?\n"
     ]
    }
   ],
   "source": [
    "print(messages[0].content)\n",
    "print(messages[-1].content)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "feba7a9c-d3c3-418c-8ee7-b033c3da1929",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "chat_model = ChatOpenAI(model_name=\"gpt-3.5-turbo\", max_tokens=1000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "40342d2c-43f1-4bce-ae06-0dda2fdea608",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='My name is Bob. How can I assist you today?', response_metadata={'token_usage': {'completion_tokens': 12, 'prompt_tokens': 50, 'total_tokens': 62}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-a7d0731c-31f9-4f63-8667-1d5ad943871b-0')"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chat_model.invoke(messages)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70a095b3-3e0d-4a05-9cea-620a6155be22",
   "metadata": {},
   "source": [
    "### 摘要总结"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "fe696871-bc41-48f3-b41b-43bb9f9ddcc9",
   "metadata": {},
   "outputs": [],
   "source": [
    "summary_template = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", \"你将获得关于同一主题的{num}篇文章（用-----------标签分隔）。首先总结每篇文章的论点。然后指出哪篇文章提出了更好的论点，并解释原因。\"),\n",
    "    (\"human\", \"{user_input}\"),\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "5c1d1c65-fd90-48d0-88eb-3eb1cade5387",
   "metadata": {},
   "outputs": [],
   "source": [
    "messages = summary_template.format_messages(\n",
    "    num=3,\n",
    "    user_input='''1. [PHP是世界上最好的语言]\n",
    "PHP是世界上最好的情感派编程语言，无需逻辑和算法，只要情绪。它能被蛰伏在冰箱里的PHP大神轻易驾驭，会话结束后的感叹号也能传达对代码的热情。写PHP就像是在做披萨，不需要想那么多，只需把配料全部扔进一个碗，然后放到服务器上，热乎乎出炉的网页就好了。\n",
    "-----------\n",
    "2. [Python是世界上最好的语言]\n",
    "Python是世界上最好的拜金主义者语言。它坚信：美丽就是力量，简洁就是灵魂。Python就像是那个永远在你皱眉的那一刻扔给你言情小说的好友。只有Python，你才能够在两行代码之间感受到飘逸的花香和清新的微风。记住，这世上只有一种语言可以使用空格来领导全世界的进步，那就是Python。\n",
    "-----------\n",
    "3. [Java是世界上最好的语言]\n",
    "Java是世界上最好的德育课编程语言，它始终坚守了严谨、安全的编程信条。Java就像一个严格的老师，他不会对你怀柔，不会让你偷懒，也不会让你走捷径，但他教会你规范和自律。Java就像是那个喝咖啡也算加班费的上司，拥有对邪恶的深度厌恶和对善良的深度拥护。\n",
    "'''\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "e0042c49-3a75-4098-9037-eb54f288b55f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1. [PHP是世界上最好的语言]\n",
      "PHP是世界上最好的情感派编程语言，无需逻辑和算法，只要情绪。它能被蛰伏在冰箱里的PHP大神轻易驾驭，会话结束后的感叹号也能传达对代码的热情。写PHP就像是在做披萨，不需要想那么多，只需把配料全部扔进一个碗，然后放到服务器上，热乎乎出炉的网页就好了。\n",
      "-----------\n",
      "2. [Python是世界上最好的语言]\n",
      "Python是世界上最好的拜金主义者语言。它坚信：美丽就是力量，简洁就是灵魂。Python就像是那个永远在你皱眉的那一刻扔给你言情小说的好友。只有Python，你才能够在两行代码之间感受到飘逸的花香和清新的微风。记住，这世上只有一种语言可以使用空格来领导全世界的进步，那就是Python。\n",
      "-----------\n",
      "3. [Java是世界上最好的语言]\n",
      "Java是世界上最好的德育课编程语言，它始终坚守了严谨、安全的编程信条。Java就像一个严格的老师，他不会对你怀柔，不会让你偷懒，也不会让你走捷径，但他教会你规范和自律。Java就像是那个喝咖啡也算加班费的上司，拥有对邪恶的深度厌恶和对善良的深度拥护。\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(messages[-1].content)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "fee264c1-941b-42b5-a737-9320eb4f5d71",
   "metadata": {},
   "outputs": [],
   "source": [
    "chat_result = chat_model.invoke(messages)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "92ada5fc-bee1-4e66-9f87-7d454491108a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "第一篇文章认为PHP是最好的编程语言，强调了它的情感化特点和写代码的简便性。第二篇文章认为Python是最好的编程语言，强调了它的美感和简洁性。第三篇文章认为Java是最好的编程语言，强调了它的严谨性和安全性。\n",
      "\n",
      "在这三篇文章中，第三篇关于Java的论点提出了更有说服力的观点。它强调了Java作为一种德育课编程语言的特性，比如严谨、安全、规范和自律。这些特性在软件开发中非常重要，尤其在大型项目中更为突出。与情感化或美感相比，严谨性和安全性更符合编程语言应该具备的基本属性。\n",
      "\n",
      "因此，第三篇关于Java的论点提出了更好的观点，因为它强调了编程语言应该具备的重要特性，而不只是情感或美感。\n"
     ]
    }
   ],
   "source": [
    "print(chat_result.content)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "dc089de4-84e1-4d50-91e5-6d49ade2cea1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[SystemMessage(content='你将获得关于同一主题的2篇文章（用-----------标签分隔）。首先总结每篇文章的论点。然后指出哪篇文章提出了更好的论点，并解释原因。'), HumanMessage(content='1.认为“道可道”中的第一个“道”，指的是道理，如仁义礼智之类；“可道”中的“道”，指言说的意思；“常道”，指恒久存在的“道”。因此，所谓“道可道，非常道”，指的是可以言说的道理，不是恒久存在的“道”，恒久存在的“道”不可言说。如苏辙说：“莫非道也。而可道者不可常，惟不可道，而后可常耳。今夫仁义礼智，此道之可道者也。然而仁不可以为义，而礼不可以为智，可道之不可常如此。……而道常不变，不可道之能常如此。”蒋锡昌说：“此道为世人所习称之道，即今人所谓‘道理’也，第一‘道’字应从是解。《广雅·释诂》二：‘道，说也’，第二‘道’字应从是解。‘常’乃真常不易之义，在文法上为区别词。……第三‘道’字即二十五章‘道法自然’之‘道’，……乃老子学说之总名也”。陈鼓应说：“第一个‘道’字是人们习称之道，即今人所谓‘道理’。第二个‘道’字，是指言说的意思。第三个‘道’字，是老子哲学上的专有名词，在本章它意指构成宇宙的实体与动力。……‘常道’之‘常’，为真常、永恒之意。……可以用言词表达的道，就不是常道”。\\n-----------\\n2.认为“道可道”中的第一个“道”，指的是宇宙万物的本原；“可道”中的“道”，指言说的意思；“常道”，指恒久存在的“道”。因此，“道可道，非常道”，指可以言说的“道”，就不是恒久存在的“道”。如张默生说：“‘道’，指宇宙的本体而言。……‘常’，是经常不变的意思。……可以说出来的道，便不是经常不变的道”。董平说：“第一个‘道’字与‘可道’之‘道’，内涵并不相同。第一个‘道’字，是老子所揭示的作为宇宙本根之‘道’；‘可道’之‘道’，则是‘言说’的意思。……这里的大意就是说：凡一切可以言说之‘道’，都不是‘常道’或永恒之‘道’”。汤漳平等说：“第一句中的三个‘道’，第一、三均指形上之‘道’，中间的‘道’作动词，为可言之义。……道可知而可行，但非恒久不变之道”。\\n--------\\n3.认为“道可道”中的第一个“道”，指的是宇宙万物的本原；“可道”中的“道”，指言说的意思；“常道”，则指的是平常人所讲之道、常俗之道。因此，“道可道，非常道”，指“道”是可以言说的，但它不是平常人所谓的道或常俗之道。如李荣说：“道者，虚极之理也。夫论虚极之理，不可以有无分其象，不可以上下格其真。……圣人欲坦兹玄路，开以教门，借圆通之名，目虚极之理，以理可名，称之可道。故曰‘吾不知其名，字之曰道’。非常道者，非是人间常俗之道也。人间常俗之道，贵之以礼义，尚之以浮华，丧身以成名，忘己而徇利。”司马光说：“世俗之谈道者，皆曰道体微妙，不可名言。老子以为不然，曰道亦可言道耳，然非常人之所谓道也。……常人之所谓道者，凝滞于物。”裘锡圭说：“到目前为止，可以说，几乎从战国开始，大家都把‘可道’之‘道’……看成老子所否定的，把‘常道’‘常名’看成老子所肯定的。这种看法其实有它不合理的地方，……‘道’是可以说的。《老子》这个《道经》第一章，开宗明义是要讲他的‘道’。第一个‘道’字，理所应当，也是讲他要讲的‘道’：道是可以言说的。……那么这个‘恒’字应该怎么讲？我认为很简单，‘恒’字在古代作定语用，经常是‘平常’‘恒常’的意思。……‘道’是可以言说的，但是我要讲的这个‘道’，不是‘恒道’，它不是一般人所讲的‘道’。\\n')]\n"
     ]
    }
   ],
   "source": [
    "messages = summary_template.format_messages(\n",
    "    num=2,\n",
    "    user_input='''1.认为“道可道”中的第一个“道”，指的是道理，如仁义礼智之类；“可道”中的“道”，指言说的意思；“常道”，指恒久存在的“道”。因此，所谓“道可道，非常道”，指的是可以言说的道理，不是恒久存在的“道”，恒久存在的“道”不可言说。如苏辙说：“莫非道也。而可道者不可常，惟不可道，而后可常耳。今夫仁义礼智，此道之可道者也。然而仁不可以为义，而礼不可以为智，可道之不可常如此。……而道常不变，不可道之能常如此。”蒋锡昌说：“此道为世人所习称之道，即今人所谓‘道理’也，第一‘道’字应从是解。《广雅·释诂》二：‘道，说也’，第二‘道’字应从是解。‘常’乃真常不易之义，在文法上为区别词。……第三‘道’字即二十五章‘道法自然’之‘道’，……乃老子学说之总名也”。陈鼓应说：“第一个‘道’字是人们习称之道，即今人所谓‘道理’。第二个‘道’字，是指言说的意思。第三个‘道’字，是老子哲学上的专有名词，在本章它意指构成宇宙的实体与动力。……‘常道’之‘常’，为真常、永恒之意。……可以用言词表达的道，就不是常道”。\n",
    "-----------\n",
    "2.认为“道可道”中的第一个“道”，指的是宇宙万物的本原；“可道”中的“道”，指言说的意思；“常道”，指恒久存在的“道”。因此，“道可道，非常道”，指可以言说的“道”，就不是恒久存在的“道”。如张默生说：“‘道’，指宇宙的本体而言。……‘常’，是经常不变的意思。……可以说出来的道，便不是经常不变的道”。董平说：“第一个‘道’字与‘可道’之‘道’，内涵并不相同。第一个‘道’字，是老子所揭示的作为宇宙本根之‘道’；‘可道’之‘道’，则是‘言说’的意思。……这里的大意就是说：凡一切可以言说之‘道’，都不是‘常道’或永恒之‘道’”。汤漳平等说：“第一句中的三个‘道’，第一、三均指形上之‘道’，中间的‘道’作动词，为可言之义。……道可知而可行，但非恒久不变之道”。\n",
    "--------\n",
    "3.认为“道可道”中的第一个“道”，指的是宇宙万物的本原；“可道”中的“道”，指言说的意思；“常道”，则指的是平常人所讲之道、常俗之道。因此，“道可道，非常道”，指“道”是可以言说的，但它不是平常人所谓的道或常俗之道。如李荣说：“道者，虚极之理也。夫论虚极之理，不可以有无分其象，不可以上下格其真。……圣人欲坦兹玄路，开以教门，借圆通之名，目虚极之理，以理可名，称之可道。故曰‘吾不知其名，字之曰道’。非常道者，非是人间常俗之道也。人间常俗之道，贵之以礼义，尚之以浮华，丧身以成名，忘己而徇利。”司马光说：“世俗之谈道者，皆曰道体微妙，不可名言。老子以为不然，曰道亦可言道耳，然非常人之所谓道也。……常人之所谓道者，凝滞于物。”裘锡圭说：“到目前为止，可以说，几乎从战国开始，大家都把‘可道’之‘道’……看成老子所否定的，把‘常道’‘常名’看成老子所肯定的。这种看法其实有它不合理的地方，……‘道’是可以说的。《老子》这个《道经》第一章，开宗明义是要讲他的‘道’。第一个‘道’字，理所应当，也是讲他要讲的‘道’：道是可以言说的。……那么这个‘恒’字应该怎么讲？我认为很简单，‘恒’字在古代作定语用，经常是‘平常’‘恒常’的意思。……‘道’是可以言说的，但是我要讲的这个‘道’，不是‘恒道’，它不是一般人所讲的‘道’。\n",
    "'''\n",
    ")\n",
    "print(messages)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "c048f55c-72f7-463d-af87-9aca561ddf4b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "第一篇文章认为，“道可道”中的第一个“道”指的是道理，第二个“道”指言说的意思，而“常道”指恒久存在的道。因此，“道可道，非常道”指的是可以言说的道理，并非恒久存在的道。文章引用了苏辙、蒋锡昌和陈鼓应等人的观点，阐述了这一解读。\n",
      "\n",
      "第二篇文章认为，“道可道”中的第一个“道”指的是宇宙万物的本原，第二个“道”指言说的意思，而“常道”指恒久存在的道。因此，“道可道，非常道”指的是可以言说的道，并非恒久存在的道。文章引用了张默生、董平和汤漳平等人的观点，支持了这一观点。\n",
      "\n",
      "在这两篇文章中，第二篇提出的论点更为清晰和一致。它明确指出第一个“道”指的是宇宙的本原，第二个“道”指言说的意思，而“常道”指恒久存在的道。这种解读在文章中得到了不同学者的支持，使得论点更加有力和可信。因此，第二篇文章提出了更好的论点。\n"
     ]
    }
   ],
   "source": [
    "chat_result = chat_model.invoke(messages)\n",
    "print(chat_result.content)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "211d7fea-ff97-4c92-a478-f14be0cbd00b",
   "metadata": {},
   "outputs": [],
   "source": [
    "messages = summary_template.format_messages(\n",
    "    num=2,\n",
    "    user_input='''1.认为“道可道”中的第一个“道”，指的是道理，如仁义礼智之类；“可道”中的“道”，指言说的意思；“常道”，指恒久存在的“道”。因此，所谓“道可道，非常道”，指的是可以言说的道理，不是恒久存在的“道”，恒久存在的“道”不可言说。如苏辙说：“莫非道也。而可道者不可常，惟不可道，而后可常耳。今夫仁义礼智，此道之可道者也。然而仁不可以为义，而礼不可以为智，可道之不可常如此。……而道常不变，不可道之能常如此。”蒋锡昌说：“此道为世人所习称之道，即今人所谓‘道理’也，第一‘道’字应从是解。《广雅·释诂》二：‘道，说也’，第二‘道’字应从是解。‘常’乃真常不易之义，在文法上为区别词。……第三‘道’字即二十五章‘道法自然’之‘道’，……乃老子学说之总名也”。陈鼓应说：“第一个‘道’字是人们习称之道，即今人所谓‘道理’。第二个‘道’字，是指言说的意思。第三个‘道’字，是老子哲学上的专有名词，在本章它意指构成宇宙的实体与动力。……‘常道’之‘常’，为真常、永恒之意。……可以用言词表达的道，就不是常道”。\n",
    "-----------\n",
    "2.认为“道可道”中的第一个“道”，指的是宇宙万物的本原；“可道”中的“道”，指言说的意思；“常道”，指恒久存在的“道”。因此，“道可道，非常道”，指可以言说的“道”，就不是恒久存在的“道”。如张默生说：“‘道’，指宇宙的本体而言。……‘常’，是经常不变的意思。……可以说出来的道，便不是经常不变的道”。董平说：“第一个‘道’字与‘可道’之‘道’，内涵并不相同。第一个‘道’字，是老子所揭示的作为宇宙本根之‘道’；‘可道’之‘道’，则是‘言说’的意思。……这里的大意就是说：凡一切可以言说之‘道’，都不是‘常道’或永恒之‘道’”。汤漳平等说：“第一句中的三个‘道’，第一、三均指形上之‘道’，中间的‘道’作动词，为可言之义。……道可知而可行，但非恒久不变之道”。\n",
    "'''\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "c524c96a-a754-47fd-bfc0-45aafd642d2d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "第一篇文章认为，“道可道，非常道”中的第一个“道”指的是可以言说的道理，第二个“道”指言说的意思，而“常道”指恒久存在的道。文章引用了苏辙、蒋锡昌和其他学者的观点，解释了“道可道，非常道”的含义，认为可以言说的道理并非恒久存在的道。\n",
      "\n",
      "第二篇文章则认为，“道可道，非常道”中的第一个“道”指的是宇宙万物的本原，第二个“道”指言说的意思，而“常道”指恒久存在的道。文章引用了张默生、董平和汤漳平等学者的观点，指出凡是可以言说的道并非恒久存在的道。\n",
      "\n",
      "在比较两篇文章的论点时，我认为第二篇文章提出了更好的论点。这是因为第二篇文章更加强调了“道”作为宇宙万物的本原，与“道”作为言说的意思之间的区别，更清晰地解释了“道可道，非常道”的含义。同时，第二篇文章引用了更多学者的观点，从不同角度深入探讨了这一问题，使读者更容易理解“道”的复杂性和深刻含义。\n"
     ]
    }
   ],
   "source": [
    "chat_result = chat_model.invoke(messages)\n",
    "print(chat_result.content)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02f6280a-f38d-46f8-a5ff-06f98025f46e",
   "metadata": {},
   "source": [
    "### 使用 FewShotPromptTemplate 类生成 Few-shot Prompt \n",
    "\n",
    "构造 few-shot prompt 的方法通常有两种：\n",
    "- 从示例集（set of examples）中手动选择；\n",
    "- 通过示例选择器（Example Selector）自动选择."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "d086861b-b576-446e-bf2f-89544e500c22",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.prompts.prompt import PromptTemplate\n",
    "\n",
    "\n",
    "examples = [\n",
    "  {\n",
    "    \"question\": \"谁活得更久，穆罕默德·阿里还是艾伦·图灵？\",\n",
    "    \"answer\": \n",
    "\"\"\"\n",
    "这里需要进一步的问题吗：是的。\n",
    "追问：穆罕默德·阿里去世时多大了？\n",
    "中间答案：穆罕默德·阿里去世时74岁。\n",
    "追问：艾伦·图灵去世时多大了？\n",
    "中间答案：艾伦·图灵去世时41岁。\n",
    "所以最终答案是：穆罕默德·阿里\n",
    "\"\"\"\n",
    "  },\n",
    "  {\n",
    "    \"question\": \"craigslist的创始人是什么时候出生的？\",\n",
    "    \"answer\": \n",
    "\"\"\"\n",
    "这里需要进一步的问题吗：是的。\n",
    "追问：谁是craigslist的创始人？\n",
    "中间答案：Craigslist是由Craig Newmark创办的。\n",
    "追问：Craig Newmark是什么时候出生的？\n",
    "中间答案：Craig Newmark出生于1952年12月6日。\n",
    "所以最终答案是：1952年12月6日\n",
    "\"\"\"\n",
    "  },\n",
    "  {\n",
    "    \"question\": \"乔治·华盛顿的外祖父是谁？\",\n",
    "    \"answer\":\n",
    "\"\"\"\n",
    "这里需要进一步的问题吗：是的。\n",
    "追问：谁是乔治·华盛顿的母亲？\n",
    "中间答案：乔治·华盛顿的母亲是Mary Ball Washington。\n",
    "追问：Mary Ball Washington的父亲是谁？\n",
    "中间答案：Mary Ball Washington的父亲是Joseph Ball。\n",
    "所以最终答案是：Joseph Ball\n",
    "\"\"\"\n",
    "  },\n",
    "  {\n",
    "    \"question\": \"《大白鲨》和《皇家赌场》的导演是同一个国家的吗？\",\n",
    "    \"answer\":\n",
    "\"\"\"\n",
    "这里需要进一步的问题吗：是的。\n",
    "追问：谁是《大白鲨》的导演？\n",
    "中间答案：《大白鲨》的导演是Steven Spielberg。\n",
    "追问：Steven Spielberg来自哪里？\n",
    "中间答案：美国。\n",
    "追问：谁是《皇家赌场》的导演？\n",
    "中间答案：《皇家赌场》的导演是Martin Campbell。\n",
    "追问：Martin Campbell来自哪里？\n",
    "中间答案：新西兰。\n",
    "所以最终答案是：不是\n",
    "\"\"\"\n",
    "  }\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "9a722158-fc50-4d34-827e-224e9f6e5e7d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Question: 谁活得更久，穆罕默德·阿里还是艾伦·图灵？\n",
      "\n",
      "这里需要进一步的问题吗：是的。\n",
      "追问：穆罕默德·阿里去世时多大了？\n",
      "中间答案：穆罕默德·阿里去世时74岁。\n",
      "追问：艾伦·图灵去世时多大了？\n",
      "中间答案：艾伦·图灵去世时41岁。\n",
      "所以最终答案是：穆罕默德·阿里\n",
      "\n"
     ]
    }
   ],
   "source": [
    "example_prompt = PromptTemplate(\n",
    "    input_variables=[\"question\", \"answer\"],\n",
    "    template=\"Question: {question}\\n{answer}\"\n",
    ")\n",
    "\n",
    "# **examples[0] 是将examples[0] 字典的键值对（question-answer）解包并传递给format，作为函数参数\n",
    "print(example_prompt.format(**examples[0]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "e4b8892e-c8a8-4c58-89be-5e331f040a62",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "input_variables=['answer', 'question'] template='Question: {question}\\n{answer}'\n"
     ]
    }
   ],
   "source": [
    "print(example_prompt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "e0c5861c-a383-4bd5-a3ee-27258c774fd0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Question: 《大白鲨》和《皇家赌场》的导演是同一个国家的吗？\n",
      "\n",
      "这里需要进一步的问题吗：是的。\n",
      "追问：谁是《大白鲨》的导演？\n",
      "中间答案：《大白鲨》的导演是Steven Spielberg。\n",
      "追问：Steven Spielberg来自哪里？\n",
      "中间答案：美国。\n",
      "追问：谁是《皇家赌场》的导演？\n",
      "中间答案：《皇家赌场》的导演是Martin Campbell。\n",
      "追问：Martin Campbell来自哪里？\n",
      "中间答案：新西兰。\n",
      "所以最终答案是：不是\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(example_prompt.format(**examples[-1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22a053e2-c8e0-4cd8-9648-01a00057d1b5",
   "metadata": {},
   "source": [
    "#### 关于解包的示例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "32806357-32c4-4e1c-ad6c-8b664bbf3004",
   "metadata": {
    "jupyter": {
     "source_hidden": true
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Question: 谁活得更久，穆罕默德·阿里还是艾伦·图灵？\n",
      "Answer: \n",
      "这里需要进一步的问题吗：是的。\n",
      "追问：穆罕默德·阿里去世时多大了？\n",
      "中间答案：穆罕默德·阿里去世时74岁。\n",
      "追问：艾伦·图灵去世时多大了？\n",
      "中间答案：艾伦·图灵去世时41岁。\n",
      "所以最终答案是：穆罕默德·阿里\n",
      "\n"
     ]
    }
   ],
   "source": [
    "def print_info(question, answer):\n",
    "    print(f\"Question: {question}\")\n",
    "    print(f\"Answer: {answer}\")\n",
    "\n",
    "print_info(**examples[0]) "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "759cc0de-1065-4997-ad78-b3ad4325585c",
   "metadata": {},
   "source": [
    "### 生成 Few-shot Prompt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "ab9d0a1c-ca67-4b97-9095-011102c06046",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Question: 谁活得更久，穆罕默德·阿里还是艾伦·图灵？\n",
      "\n",
      "这里需要进一步的问题吗：是的。\n",
      "追问：穆罕默德·阿里去世时多大了？\n",
      "中间答案：穆罕默德·阿里去世时74岁。\n",
      "追问：艾伦·图灵去世时多大了？\n",
      "中间答案：艾伦·图灵去世时41岁。\n",
      "所以最终答案是：穆罕默德·阿里\n",
      "\n",
      "\n",
      "Question: craigslist的创始人是什么时候出生的？\n",
      "\n",
      "这里需要进一步的问题吗：是的。\n",
      "追问：谁是craigslist的创始人？\n",
      "中间答案：Craigslist是由Craig Newmark创办的。\n",
      "追问：Craig Newmark是什么时候出生的？\n",
      "中间答案：Craig Newmark出生于1952年12月6日。\n",
      "所以最终答案是：1952年12月6日\n",
      "\n",
      "\n",
      "Question: 乔治·华盛顿的外祖父是谁？\n",
      "\n",
      "这里需要进一步的问题吗：是的。\n",
      "追问：谁是乔治·华盛顿的母亲？\n",
      "中间答案：乔治·华盛顿的母亲是Mary Ball Washington。\n",
      "追问：Mary Ball Washington的父亲是谁？\n",
      "中间答案：Mary Ball Washington的父亲是Joseph Ball。\n",
      "所以最终答案是：Joseph Ball\n",
      "\n",
      "\n",
      "Question: 《大白鲨》和《皇家赌场》的导演是同一个国家的吗？\n",
      "\n",
      "这里需要进一步的问题吗：是的。\n",
      "追问：谁是《大白鲨》的导演？\n",
      "中间答案：《大白鲨》的导演是Steven Spielberg。\n",
      "追问：Steven Spielberg来自哪里？\n",
      "中间答案：美国。\n",
      "追问：谁是《皇家赌场》的导演？\n",
      "中间答案：《皇家赌场》的导演是Martin Campbell。\n",
      "追问：Martin Campbell来自哪里？\n",
      "中间答案：新西兰。\n",
      "所以最终答案是：不是\n",
      "\n",
      "\n",
      "Question: 玛丽·波尔·华盛顿的父亲是谁?\n"
     ]
    }
   ],
   "source": [
    "# 导入 FewShotPromptTemplate 类\n",
    "from langchain.prompts.few_shot import FewShotPromptTemplate\n",
    "\n",
    "# 创建一个 FewShotPromptTemplate 对象\n",
    "few_shot_prompt = FewShotPromptTemplate(\n",
    "    examples=examples,           # 使用前面定义的 examples 作为范例\n",
    "    example_prompt=example_prompt, # 使用前面定义的 example_prompt 作为提示模板\n",
    "    suffix=\"Question: {input}\",    # 后缀模板，其中 {input} 会被替换为实际输入\n",
    "    input_variables=[\"input\"]     # 定义输入变量的列表\n",
    ")\n",
    "\n",
    "# 使用给定的输入格式化 prompt，并打印结果\n",
    "# 这里的 {input} 将被 \"玛丽·波尔·华盛顿的父亲是谁?\" 替换\n",
    "print(few_shot_prompt.format(input=\"玛丽·波尔·华盛顿的父亲是谁?\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c559cecd-ad58-495e-92ff-765233888ad8",
   "metadata": {},
   "source": [
    "## 示例选择器 Example Selectors\n",
    "\n",
    "**如果你有大量的参考示例，就得选择哪些要包含在提示中。最好还是根据某种条件或者规则来自动选择，Example Selector 是负责这个任务的类。**\n",
    "\n",
    "BaseExampleSelector 定义如下：\n",
    "\n",
    "```python\n",
    "class BaseExampleSelector(ABC):\n",
    "    \"\"\"用于选择包含在提示中的示例的接口。\"\"\"\n",
    "\n",
    "    @abstractmethod\n",
    "    def select_examples(self, input_variables: Dict[str, str]) -> List[dict]:\n",
    "        \"\"\"根据输入选择要使用的示例。\"\"\"\n",
    "\n",
    "```\n",
    "\n",
    "`ABC` 是 Python 中的 `abc` 模块中的一个缩写，它表示 \"Abstract Base Class\"（抽象基类）。在 Python 中，抽象基类用于定义其他类必须遵循的基本接口或蓝图，但不能直接实例化。其主要目的是为了提供一种形式化的方式来定义和检查子类的接口。\n",
    "\n",
    "使用抽象基类的几点关键信息：\n",
    "\n",
    "1. **抽象方法**：在抽象基类中，你可以定义抽象方法，它没有实现（也就是说，它没有方法体）。任何继承该抽象基类的子类都必须提供这些抽象方法的实现。\n",
    "\n",
    "2. **不能直接实例化**：你不能直接创建抽象基类的实例。试图这样做会引发错误。它们的主要目的是为了被继承，并在子类中实现其方法。\n",
    "\n",
    "3. **强制子类实现**：如果子类没有实现所有的抽象方法，那么试图实例化该子类也会引发错误。这确保了继承抽象基类的所有子类都遵循了预定的接口。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "627b028b-3762-4b3d-99ca-98c88b175677",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "b8886dad-54b9-4fdf-8481-8abdd506676d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入需要的模块和类\n",
    "from langchain.prompts.example_selector import SemanticSimilarityExampleSelector\n",
    "from langchain.vectorstores import Chroma\n",
    "from langchain_openai import OpenAIEmbeddings\n",
    "from langchain.prompts import FewShotPromptTemplate, PromptTemplate\n",
    "\n",
    "# 定义一个提示模板\n",
    "example_prompt = PromptTemplate(\n",
    "    input_variables=[\"input\", \"output\"],     # 输入变量的名字\n",
    "    template=\"Input: {input}\\nOutput: {output}\",  # 实际的模板字符串\n",
    ")\n",
    "\n",
    "# 这是一个假设的任务示例列表，用于创建反义词\n",
    "examples = [\n",
    "    {\"input\": \"happy\", \"output\": \"sad\"},\n",
    "    {\"input\": \"tall\", \"output\": \"short\"},\n",
    "    {\"input\": \"energetic\", \"output\": \"lethargic\"},\n",
    "    {\"input\": \"sunny\", \"output\": \"gloomy\"},\n",
    "    {\"input\": \"windy\", \"output\": \"calm\"},\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "77160159-3ae8-467e-a27c-a670952967c8",
   "metadata": {},
   "source": [
    "### Pandas 相关包首次导入错误后，再次执行即可正确导入"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "e15785f7-fa11-4b2b-a668-9838b14839ff",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# 从给定的示例中创建一个语义相似性选择器\n",
    "example_selector = SemanticSimilarityExampleSelector.from_examples(\n",
    "    examples,                          # 可供选择的示例列表\n",
    "    OpenAIEmbeddings(),                # 用于生成嵌入向量的嵌入类，用于衡量语义相似性\n",
    "    Chroma,                            # 用于存储嵌入向量并进行相似性搜索的 VectorStore 类\n",
    "    k=1                                # 要生成的示例数量\n",
    ")\n",
    "\n",
    "# 创建一个 FewShotPromptTemplate 对象\n",
    "similar_prompt = FewShotPromptTemplate(\n",
    "    example_selector=example_selector,  # 提供一个 ExampleSelector 替代示例\n",
    "    example_prompt=example_prompt,      # 前面定义的提示模板\n",
    "    prefix=\"Give the antonym of every input\", # 前缀模板\n",
    "    suffix=\"Input: {adjective}\\nOutput:\",     # 后缀模板\n",
    "    input_variables=[\"adjective\"],           # 输入变量的名字\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "7c24dd07-c3fc-4c85-9481-a6d163723b72",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Give the antonym of every input\n",
      "\n",
      "Input: happy\n",
      "Output: sad\n",
      "\n",
      "Input: worried\n",
      "Output:\n"
     ]
    }
   ],
   "source": [
    "# 输入是一种感受，所以应该选择 happy/sad 的示例。\n",
    "print(similar_prompt.format(adjective=\"worried\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "0c4a113f-0719-4534-8932-1a363991da84",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Give the antonym of every input\n",
      "\n",
      "Input: tall\n",
      "Output: short\n",
      "\n",
      "Input: long\n",
      "Output:\n"
     ]
    }
   ],
   "source": [
    "# 输入是一种度量，所以应该选择 tall/short的示例。\n",
    "print(similar_prompt.format(adjective=\"long\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "760abbb9-c00c-43fb-8c73-341c3993f720",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Give the antonym of every input\n",
      "\n",
      "Input: windy\n",
      "Output: calm\n",
      "\n",
      "Input: rain\n",
      "Output:\n"
     ]
    }
   ],
   "source": [
    "print(similar_prompt.format(adjective=\"rain\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6da0fda7-fbef-4ded-8eb5-2a482c0475d5",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
